/*	Please notice that hcommit is in a very early state of development. At the moment, only hcommit
	can be considered usable and most of it is still missing. If you intent to use hcommit,
	I therefore highly recommend to encrypt the files with GnuPG and disguise it with some trustable
	steganographic tool until SDTP is in a state where it can be used alone. */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <gcrypt.h>
#include "base64.h"
#include "string_tools.h"
#include "../txt/hcommit.str"

#define BUFFER_SIZE 1152

const char* mandatory_params[2][2] = {{"content-type", "hash-commitment"}, {"version", "1"}};

void chk_filename(char* filename)
{
	if (strlen(filename) > 13)
	{
		printf(LENGTH_LIMIT_13);
		exit(EXIT_SUCCESS);	// Not FAILURE, since its rather an
					        // information and not a real error
	}
	if (strstr(filename, "c_") || strstr(filename, "r_"))
	{
		printf(FILENAME_PREFIX);
		exit(EXIT_FAILURE);
	}

	return;
}

// TODO This fn is too general to be implemented here. Move to strtools.c ?

void get_val_by_param(char* buffer, const char* param, char** index, size_t index_s)
{
	int i;
	int once = 0;
	for (i = 0; i < index_s; i++)
	{
		if (strstr(index[i], param) == index[i] && once != 0)
			abort();	// If a param does not occure once

		if (strstr(index[i], param) == index[i] && once == 0)
		{
			strcpy(buffer, strchr(index[i], ':') + 1);
			once = 1;
		}
	}
}

void create_commitment(char* filename)
{
	char descr[64];
	char comit[1024 + 16];
	char sha_h[32];
	char entrp[12];
	char filen[16];
	char cfile[16] = "c_";
	char rfile[16] = "r_";
	char ibuff[2];

	char *sha_h_b64;
	int  sha_h64len;
	char *entrp_b64;
	int  entrp64ln;
	FILE *f, *c_exists, *r_exists;

	chk_filename(filename);

	strcpy(filen, filename);
	strcat(cfile, filen);
	strcat(rfile, filen);

	c_exists = fopen(cfile, "r");
	r_exists = fopen(rfile, "r");

	if (c_exists || r_exists)
	{
		printf(str_table[0], filen);
		fgets(ibuff, 2, stdin);
		while((getchar())!='\n');
		if (tolower(ibuff[0]) != 'y')
		{
			exit(EXIT_SUCCESS);
		}

		fclose(c_exists);
		fclose(r_exists);
	}

	printf(ENTER_DESCRIPTION);
	fgets(descr, 64, stdin);
	strtok(descr, "\n");
	printf(ENTER_COMMITMENT);
	fgets(comit, 1024, stdin);
	strtok(comit, "\n");

	gcry_randomize(entrp, 12, GCRY_VERY_STRONG_RANDOM);
	entrp_b64 = base64(entrp, 12, &entrp64ln);
	strcat(comit, entrp_b64);

	gcry_md_hash_buffer(GCRY_MD_SHA256, sha_h, comit, strlen(comit));
	sha_h_b64 = base64(sha_h, 32, &sha_h64len);

	// TODO Blocks are similar. Simplifiy to a loop?

	f = fopen(cfile, "w");
	if (f == NULL)
	{
		printf(COULD_NOT_CREATE_S, filen);
		exit(EXIT_FAILURE);
	}

	fprintf(f, "content-type:\thash-commitment\n");
	fprintf(f, "version:\t1\n");
	fprintf(f, "type:\t\tcommit\n"); // This line differs...
	fprintf(f, "description:\t\"%s\"\n", descr);
	fprintf(f, "hash-value:\t%s\n", sha_h_b64);

	fclose(f);

	f = fopen(rfile, "w");

	if (f == NULL)
	{
		printf(COULD_NOT_CREATE_S, filen);
		exit(EXIT_FAILURE);
	}

	fprintf(f, "content-type:\thash-commitment\n");
	fprintf(f, "version:\t1\n");
	fprintf(f, "type:\t\treveal\n"); // ...from this one
	fprintf(f, "description:\t\"%s\"\n", descr);
	fprintf(f, "commitment:\t\"%s\"\n", comit);

	fclose(f);

	printf(SEND_S_KEEP_S_DONT_LOOSE, cfile, rfile);
}

void verify_commitment(char* filename)
{
	char c_file[16] = "c_";
	char r_file[16] = "r_";
	char filen[16];
	char xbuffer[BUFFER_SIZE];
	char ybuffer[BUFFER_SIZE];
	char *r_commit;
	unsigned char *sha_h[2]; // Use uchar everywhere?
	char *descr[2];
	char *data[2], *pos[2];
	char **index[2];
	size_t n[2], m = 0;
	int i = 0;
	int j;
	FILE *c_r[2];

	n[0] = 0;
	n[1] = 0;

	chk_filename(filename);
	strcpy(filen, filename);
	strcat(c_file, filename);
	strcat(r_file, filename);

	c_r[0] = fopen(c_file, "r");
	c_r[1] = fopen(r_file, "r");

	if (c_r[0] == NULL || c_r[1] == NULL)
	{
		printf(COULD_NOT_READ_SS, c_file, r_file);
		exit(EXIT_FAILURE);
	}

	for (j = 0; j < 2; j++)
	{
		memset(xbuffer, 0, BUFFER_SIZE);
		memset(ybuffer, 0, BUFFER_SIZE);

		while (fgets(xbuffer, BUFFER_SIZE, c_r[j]))
		{
			wwipe(ybuffer, xbuffer);
			n[j]++;
			m += strlen(ybuffer) + 1;	// Space for NULL byte
			memset(ybuffer, 0, BUFFER_SIZE);
		}

		rewind(c_r[j]);

		data[j] = malloc(m * sizeof(char));
		if (data[j] == NULL)
			abort();

		index[j] = malloc(n[j] * sizeof(char*));
		if (index[j] == NULL)
			abort();

		while (fgets(xbuffer, BUFFER_SIZE, c_r[j]))
		{
			wwipe(ybuffer, xbuffer);
			memset(xbuffer, 0, BUFFER_SIZE);
			sprintf(xbuffer, "%s;", ybuffer);
			strcat(data[j], xbuffer);
			memset(ybuffer, 0, BUFFER_SIZE);
		}

		pos[j] = strtok(data[j], ";");
		while (pos[j] != NULL)
		{
			index[j][i] = pos[j];
			pos[j] = strtok(NULL, ";");
			i++;
		}

		i = 0;

	}

	/* Optimazation to employ later: Use a loop and an array which has the format
	[5][2] and check generally, if param [i][0] corresponds with val [i][1]
	Also, dont just abort but do EXIT_FAILUE and show error msg */

	for (j = 0; j < 2; j++)
	{
		for (i = 0; i < 2; i++)
		{
			get_val_by_param(xbuffer, mandatory_params[i][0], index[j], n[j]);

			if (strcmp(xbuffer, mandatory_params[i][1]))
				abort();
		}
	}

	// This is specific
	get_val_by_param(xbuffer, "type", index[0], n[0]);
	if (strcmp(xbuffer, "commit"))
		abort();

	for (j = 0; j < 2; j++)
	{
		get_val_by_param(xbuffer, "description", index[j], n[j]);
		descr[j] = malloc(strlen(xbuffer) * sizeof(char));
		if (descr[j] == NULL)
			abort();
		strcpy(descr[j], xbuffer + 1);
		descr[j][strlen(descr[j]) - 1] = 0;
		// TODO use chrn to check for two quotation marks
	}

	if(strcmp(descr[0], descr[1]))
		abort();	// Not the same commitment

	get_val_by_param(xbuffer, "hash-value", index[0], n[0]);
	sha_h[0] = unbase64(xbuffer, strlen(xbuffer), &i);

	if (i != 32)	// This check and abort() only applies in case of hash
		abort();

	get_val_by_param(xbuffer, "commitment", index[1], n[1]);
	r_commit = malloc(strlen(xbuffer) * sizeof(char));
	strcpy(r_commit, xbuffer + 1);
	r_commit[strlen(r_commit) - 1] = 0;

	// printf("%s", r_commit);

	sha_h[1] = malloc(32);

	gcry_md_hash_buffer(GCRY_MD_SHA256, sha_h[1], r_commit, strlen(r_commit));

	if (memcmp(sha_h[0], sha_h[1], 32))
		printf(INVALID_COMMITMENT);

	else
	{
		r_commit[strlen(r_commit) - 16] = 0;
		printf(VALID_COMMITMENT_S_DESCR_S, r_commit, descr[1]);
	}

	fclose(c_r[0]);
	fclose(c_r[1]);

	for (j = 0; j < 2; j++)
	{
        free(data[j]);
        free(index[j]);
        free(descr[j]);
    }

    free(r_commit);
    free(sha_h[1]);

}

int main(int argc, char** argv)
{
	char filename[16];

	if (argc == 3)
		strcpy(filename, argv[2]);

	else if (argc == 2)
	{
		printf(NO_FILENAME);
		fgets(filename, 16, stdin);
		strtok(filename, "\n");
	}
	else
	{
		printf(USAGE_S, argv[0]);
		exit(EXIT_SUCCESS);
	}

	if (!strcmp(argv[1], "-c"))
		create_commitment(filename);
	if (!strcmp(argv[1], "-v"))
		verify_commitment(filename);

	exit(EXIT_SUCCESS);
}